"""
Copyright (C) 2015  Mattias Ugelvik
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

import unittest
import contex
import re

class TestStringContext(unittest.TestCase):
    def setUp(self):
        self.hello = contex.T('Hello, ho ho')
        self.name  = contex.T('Bob Bobbison!')
        self.empty = contex.T('')
        self.classname = 'StringContext'

    def test_focus_point(self):
        self.assertEqual(self.empty.focus_point(), slice(0, 0))
        self.assertEqual(self.empty.focus_point(do_tuple=True), (0, 0))
        self.assertEqual(self.name.find('Bobbison').focus_point(), slice(4, 12))
        self.assertEqual(self.name.focus_point(), slice(0, 13))
        self.assertEqual(self.name.focus_point(do_tuple=True), (0, 13))

    def test_replace(self):
        self.assertEqual(self.hello.find(' ', True).replace('-'), 'Hello, ho-ho')
        self.assertEqual(self.hello.find(' ', True).replace('---'), 'Hello, ho---ho')
        self.assertEqual(self.hello.find(' ', True).replace(''), 'Hello, hoho')
        self.assertEqual(self.name.find('Bob').replace(lambda s: s*2), 'BobBob Bobbison!')
        self.assertEqual(self.name.replace(''), '')
        if self.classname == 'StringContext':
            self.assertEqual(self.empty.replace('YOLO'), 'YOLO')

    def test_find(self):
        self.assertIsNone(self.empty.find('Hay'))
        self.assertEqual(self.name.find('Bobb').parts(),      ('Bob ', 'Bobb', 'ison!'))
        self.assertEqual(self.name.find('Bob').parts(),       ('', 'Bob', ' Bobbison!'))
        self.assertEqual(self.name.find('Bob', True).parts(), ('Bob ', 'Bob', 'bison!'))
        self.assertIsInstance(self.empty.find(r''), contex.StringContext)
        self.assertEqual(self.name.find('Bobb').find('ob B').parts(), ('B', 'ob B', 'obbison!'))
        

    def test_search(self):
        self.assertEqual(self.hello.search(r'HO.HO', re.I).group(0).parts(), ('Hello, ', 'ho ho', ''))
        self.assertIsNone(self.hello.search(r'HA.HA', re.I))
        self.assertEqual(self.empty.search(r''), '')
        self.assertIsInstance(self.empty.search(r''), contex.MatchContext)

    def test_match(self):
        self.assertIsNone(self.hello.match(r'HO.HO', re.I))
        self.assertEqual(self.hello.match(r'He..o[,!-]').group(0).parts(), ('', 'Hello,', ' ho ho'))
        self.assertEqual(self.empty.match(r''), '')
        self.assertIsInstance(self.empty.match(r''), contex.MatchContext)



    def test_slice(self):
        self.assertEqual(self.hello[-2:].parts(),           ('Hello, ho ', 'ho', ''))
        self.assertEqual(self.hello[:-2].parts(),           ('', 'Hello, ho ', 'ho'))
        self.assertEqual(self.hello[:].parts(),             ('', 'Hello, ho ho', ''))
        self.assertEqual(self.hello[0:5].parts(),           ('', 'Hello', ', ho ho'))
        self.assertEqual(self.hello[1].parts(),             ('H', 'e', 'llo, ho ho'))
        self.assertEqual(self.hello[-1].parts(),            ('Hello, ho h', 'o', ''))
        self.assertEqual(self.hello.find('ll')[-1].parts(), ('Hello, ho h', 'o', ''))
        self.assertEqual(self.hello[0][1][2].parts(),       ('He', 'l', 'lo, ho ho'))
        self.assertEqual(self.hello[1:1].parts(),           ('H', '', 'ello, ho ho'))
        
    def test_iter(self):
        L = list('Hello, ho ho')
        self.assertEqual(list(iter(self.hello)), L)
        self.assertEqual(list(iter(self.hello.find('llo'))), L)
        self.assertEqual(list(iter(self.empty)), [])
        if self.classname == 'StringContext':
            self.assertEqual(list(iter(self.empty.replace('H'))), ['H'])

    def test_len(self):
        self.assertEqual(len(self.empty), 0)
        self.assertEqual(len(self.hello), len(str(self.hello)))
        self.assertEqual(len(self.hello.find('ho').replace('hoyo')), len(str(self.hello)) + 2)
        if self.classname == 'StringContext':
            self.assertEqual(len(self.empty.replace('H')), 1)

    def test_eq(self):
        self.assertTrue('' == self.empty)
        self.assertTrue(self.empty == '')
        self.assertTrue(self.hello == 'Hello, ho ho')
        self.assertTrue('Hello, ho ho' == self.hello)
        self.assertTrue('Hello, ho ho' == self.hello.find(', '))
        self.assertTrue(', ' == self.hello.find(', ').focus)


    def test_hash(self):
        self.assertEqual(hash(self.empty), hash(''))
        self.assertEqual(hash(self.name.find('Bobb')), hash('Bob Bobbison!'))
        d = {'Bob Bobbison!': 4}
        self.assertEqual(d[self.name.find('Bobb')], 4)
        d[self.name.find('Bobb')] = 8
        self.assertEqual(d['Bob Bobbison!'], 8)

    def test_parts(self):
        self.assertEqual(self.name.parts(), ('', 'Bob Bobbison!', ''))
        self.assertEqual(self.name.find('Bobb').parts(), ('Bob ', 'Bobb', 'ison!'))

    def test_self_compatabillity(self):
        self.assertEqual(self.hello.find(' ho ').replace(
            self.name.find('Bobb')
        ), 'Hello,Bob Bobbison!ho')

    def test_str(self):
        self.assertEqual(str(self.name.find('Bobb')), 'Bob Bobbison!')
        self.assertEqual(str(self.empty), '')

    def test_repr(self):
        if self.classname=='StringContext':
            self.assertEqual(repr(self.empty), "{}('', '', '')".format(self.classname))
            self.assertEqual(repr(self.name.find('Bobb')), "StringContext('Bob ', 'Bobb', 'ison!')")
        else:
            self.assertEqual(repr(self.empty), "<{} object; group=0, tup=('', '', '')>".format(self.classname))

        
class TestMatchContext(TestStringContext): # <- Inherits all the tests from `TestStringContext`
    
    def setUp(self):
        self.hello = contex.match('Hello, ho ho', 'Hello, ho ho')
        self.name  = contex.match('Bob Bobbison!', 'Bob Bobbison!')
        self.empty = contex.match('', '')
        self.classname = 'MatchContext'


        self.regname = contex.match('Bob Bobbison!',          r'(?P<first_name>\w+) (?P<last_name>\w+)(!)?')
        self.regname_no_exclam = contex.match('Bob Bobbison', r'(?P<first_name>\w+) (?P<last_name>\w+)(!)?')
        self.regname_no_bob = contex.match(' Bobbison!',      r'(?P<first_name>\w+)? (?P<last_name>\w+)(!)?')


        
    def test_group_spans(self):
        self.assertEqual(self.regname.group_span,
                         {0: (0, 13),
                          'first_name': (0, 3), 'last_name': (4, 12),
                          1:            (0, 3), 2:           (4, 12),
                          3:            (12, 13)
                      })

    def test_group(self):
        self.assertEqual(self.regname.group(0).parts(), ('', 'Bob Bobbison!', ''))
        self.assertEqual(self.regname.group(1).parts(),            ('', 'Bob', ' Bobbison!'))
        self.assertEqual(self.regname.group('first_name').parts(), ('', 'Bob', ' Bobbison!'))
        self.assertEqual(self.regname.group(2).parts(),           ('Bob ', 'Bobbison', '!'))
        self.assertEqual(self.regname.group('last_name').parts(), ('Bob ', 'Bobbison', '!'))
        self.assertEqual(self.regname.group(3).parts(), ('Bob Bobbison', '!', ''))

    def test_expand(self):
        self.assertEqual(self.regname.expand('{last_name}|{3:#<4}|{first_name}|{0}|{1}|{olala}', olala='HEY'),
                         'Bobbison|!###|Bob|Bob Bobbison!|Bob|HEY')
        self.assertRaises(KeyError, self.regname.expand, '{0}|{olala}')
        self.assertRaises(KeyError, self.regname_no_bob.expand, '{first_name}')
        

    def test_replace_stuff(self):
        self.assertEqual(self.regname.group('last_name').replace('').group(3).parts(),              ('Bob ', '!', ''))
        self.assertEqual(self.regname.group('last_name').replace('YO').group(3).parts(),            ('Bob YO', '!', ''))
        self.assertEqual(self.regname.group('last_name').replace('YO').group('first_name').parts(), ('', 'Bob', ' YO!'))
        self.assertRaises(ValueError, self.regname_no_exclam.group, 3)
        self.assertRaises(ValueError, self.regname_no_bob.group, 'first_name')

    def test_innermost_group_not_expand(self):
        v = contex.search('a-b-c', r'a-(?P<outer>(?P<inner>b))-c')
        self.assertEqual(v.group('outer').replace('BB').group('inner').parts(), ('a-', 'B', 'B-c'))

    def test_innermost_group_shrink(self):
        v = contex.search('a-b-c', r'a(?P<outer>-(?P<inner>b-))c')
        self.assertEqual(v.group('outer').replace('-b').group('inner').parts(), ('a-', 'b', 'c'))
        self.assertEqual(v.group('outer').replace('').group('inner').parts(), ('a', '', 'c'))

    def test_outermost_group_expand(self):
        v = contex.search('a-b-c', r'a-(?P<outer>(?P<inner>b))-c')
        self.assertEqual(v.group('inner').replace('BB').group('outer').parts(), ('a-', 'BB', '-c'))

class TestRules(unittest.TestCase):
    def setUp(self):
        self.transformer = contex.rules(r'(Photo)(?P<num>[0-9]+)_(?P<year>[0-9]{4})\.(?P<suffix>jpg)', {
            1: 'Foto',
            'num': lambda num: int(num) + 1,
            'year': lambda year: year[-2:],
            'suffix': 'jpeg',
        })
        
        self.transformer_flag = contex.rules(r'(Photo)([0-9]+)_(?P<year>[0-9]{4})\.(?P<suffix>jpg)', {
            1: 'Foto',
            2: lambda num: int(num) + 1,
            'year': lambda year: year[-2:],
            'suffix': 'jpeg'
        }, flags=re.I)

    def test_apply(self):
        self.assertEqual(self.transformer.apply('Photo25_2009.jpg'), 'Foto26_09.jpeg')
        self.assertEqual(self.transformer('Photo25_2009.jpg'), 'Foto26_09.jpeg') # __call__
        self.assertIsNone(self.transformer.apply('photo26_09.jpeg'))

    def test_apply_flag(self):
        self.assertEqual(self.transformer.apply('Photo25_2009.jpg'), 'Foto26_09.jpeg')
        self.assertEqual(self.transformer_flag.apply('photo25_2009.JPG'), 'Foto26_09.jpeg')

    def test_expand(self):
        self.assertEqual(self.transformer.expand('Photo25_2009.jpg', '{year}-{num}-{1}.{suffix}'),
                         '09-26-Foto.jpeg')
        self.assertIsNone(self.transformer.expand('random stuff', '{year}-{num}-{1}.{suffix}'))

    def test_empty_group(self):
        self.assertEqual(contex.rules(r'123(h?)', {1: 'yo'}).apply('123'), '123yo')
        self.assertEqual(contex.rules(r'123(h?)', {1: 'yo'}).apply('123h'), '123yo')

    def test_identical_group(self):
        self.assertEqual(contex.rules(r'((123)h?)', {1: 'yo', 2: 'low'}).apply('123'), 'yo')
        self.assertEqual(contex.rules(r'((123)h?)', {1: 'yo', 2: 'low'}).apply('123h'), 'yo')

    def test_zero_stuff(self):
        trans = contex.rules(r'(hey)(?P<outerest>(?P<outer>(?P<inner>a)))(?:yo)(?P<one>)(?P<two>)(?P<three>)lo', {
            1: '',
            'one': 'A', 'two': 'B', 'three': 'C',
            'inner':    lambda s: '('+s+')',
            'outer':    lambda s: '['+s+']',
            'outerest': lambda s: '{'+s+'}',
            
        })
        # Testing that the non-capturing group (?:yo) doesn't mess things up, that the zero-length groups
        # `one`, `two` and `three` have the correct ordering, and that the nested groups `inner`, `outer`
        # and `outerest` are replaced in the correct order
        self.assertEqual(trans.apply('heyayolo'), '{[(a)]}yoABClo')

    def test_repr(self):
        self.assertTrue(repr(self.transformer).startswith('<Transformer object;'))
        
if __name__ == '__main__':
    unittest.main()
